package com.fbs.job.admin.core.trigger;

import com.fbs.job.admin.core.config.FbsJobAdminConfig;
import com.fbs.job.admin.core.entity.FbsJobActuator;
import com.fbs.job.admin.core.entity.FbsJobInfo;
import com.fbs.job.admin.core.entity.FbsJobLog;
import com.fbs.job.core.enums.ExecutorBlockStrategyEnum;
import com.fbs.job.admin.core.enums.ExecutorRouteStrategyEnum;
import com.fbs.job.admin.core.enums.TriggerTypeEnum;
import com.fbs.job.admin.core.scheduler.FbsJobScheduler;
import com.fbs.job.core.biz.ExecutorBiz;
import com.fbs.job.core.biz.model.TriggerParam;
import com.fbs.job.core.model.ResultT;
import com.fbs.job.core.util.IpUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

public class FbsJobTrigger {
    private static Logger logger = LoggerFactory.getLogger(FbsJobTrigger.class);


    public static void trigger(Trigger trigger){
        // load data
        FbsJobInfo jobInfo = FbsJobAdminConfig.getAdminConfig().getFbsJobInfoMapper().getById(trigger.getJobId());
        if (jobInfo == null) {
            logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid，jobId={}", trigger.getJobId());
            return;
        }
        if (StringUtils.isNotBlank(trigger.getExecutorParam())) {
            jobInfo.setExecutorParam(trigger.getExecutorParam());
        }
        int finalFailRetryCount = trigger.getFailRetryCount()>=0?trigger.getFailRetryCount():jobInfo.getExecutorFailRetryCount();
        FbsJobActuator actuator = FbsJobAdminConfig.getAdminConfig().getFbsJobActuatorMapper().getById(jobInfo.getJobActuator());
        // cover addressList
        if (StringUtils.isNotBlank(trigger.getAddressList())) {
            actuator.setAddressType(1);
            actuator.setAddressList(trigger.getAddressList().trim());
        }
        // sharding param
        int[] shardingParam = null;
        if (trigger.getExecutorShardingParam()!=null){
            String[] shardingArr = trigger.getExecutorShardingParam().split("/");
            if (shardingArr.length==2 && NumberUtils.isDigits(shardingArr[0]) && NumberUtils.isDigits(shardingArr[1])) {
                shardingParam = new int[2];
                shardingParam[0] = Integer.valueOf(shardingArr[0]);
                shardingParam[1] = Integer.valueOf(shardingArr[1]);
            }
        }
        if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==jobInfo.getExecutorRouteStrategyEnum()
                && CollectionUtils.isNotEmpty(actuator.getRegistryList())
                && shardingParam==null) {
            for (int i = 0; i < actuator.getRegistryList().size(); i++) {
                processTrigger(actuator, jobInfo, finalFailRetryCount, trigger.getTriggerType(), i, actuator.getRegistryList().size());
            }
        } else {
            if (shardingParam == null) {
                shardingParam = new int[]{0, 1};
            }
            processTrigger(actuator, jobInfo, finalFailRetryCount, trigger.getTriggerType(), shardingParam[0], shardingParam[1]);
        }




    }

    private static void processTrigger(FbsJobActuator actuator, FbsJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total) {
        // 阻塞处理策略
        ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(),
                ExecutorBlockStrategyEnum.SERIAL_EXECUTION);  // block strategy
        //执行器路由策略
        ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);    // route strategy
        String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;
        // 1、save log-id
        FbsJobLog jobLog = new FbsJobLog();
        jobLog.setJobActuator(jobInfo.getJobActuator());
        jobLog.setJobId(jobInfo.getId());
        jobLog.setTriggerTime(new Date());
        FbsJobAdminConfig.getAdminConfig().getFbsJobLogMapper().save(jobLog);
        logger.debug(">>>>>>>>>>> fbs-job trigger start, jobId:{}", jobLog.getId());


        // 2、init trigger-param
        TriggerParam triggerParam = new TriggerParam();
        triggerParam.setJobId(jobInfo.getId());
        triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
        triggerParam.setExecutorParams(jobInfo.getExecutorParam());
        triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
        triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
        triggerParam.setLogId(jobLog.getId());
        triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
        triggerParam.setGlueType(jobInfo.getGlueType());
        triggerParam.setGlueSource(jobInfo.getGlueSource());
        triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
        triggerParam.setBroadcastIndex(index);
        triggerParam.setBroadcastTotal(total);
        // 3、init address
        String address = null;
        ResultT<String> routeAddressResult = null;
        if (CollectionUtils.isNotEmpty(actuator.getRegistryList())){
          if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum){
              if (index < actuator.getRegistryList().size()) {
                  address = actuator.getRegistryList().get(index);
              } else {
                  address = actuator.getRegistryList().get(0);
              }
          }else {
              routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, actuator.getRegistryList());
              if (routeAddressResult.getCode() == ResultT.SUCCESS_CODE) {
                  address = routeAddressResult.getData();
              }
          }
        }else {
            routeAddressResult = new ResultT<String>(ResultT.FAIL_CODE, "执行器地址为空");
        }


        // 4、trigger remote executor
        ResultT<String> triggerResult = null;
        if (address != null) {
            triggerResult = runExecutor(triggerParam, address);
        } else {
            triggerResult = new ResultT<String>(ResultT.FAIL_CODE, null);
        }

        // 5、collection trigger info
        StringBuffer triggerMsgSb = new StringBuffer();
        triggerMsgSb.append("Job trigger type").append("：").append(triggerType.getTitle());
        triggerMsgSb.append("<br>").append("Trigger machine address").append("：").append(IpUtil.getIp());
        triggerMsgSb.append("<br>").append("执行器-注册方式").append("：")
                .append( (actuator.getAddressType() == 0)?"自动注册":"手动注册" );
        triggerMsgSb.append("<br>").append("执行器-地址列表").append("：").append(actuator.getRegistryList());
        triggerMsgSb.append("<br>").append("路由策略").append("：").append(executorRouteStrategyEnum.getTitle());
        if (shardingParam != null) {
            triggerMsgSb.append("("+shardingParam+")");
        }
        triggerMsgSb.append("<br>").append("阻塞处理策略").append("：").append(blockStrategy.getTitle());
        triggerMsgSb.append("<br>").append("任务超时时间").append("：").append(jobInfo.getExecutorTimeout());
        triggerMsgSb.append("<br>").append("失败重试次数").append("：").append(finalFailRetryCount);

        triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ "触发调度" +"<<<<<<<<<<< </span><br>")
                .append((routeAddressResult!=null&&routeAddressResult.getMessage()!=null)?routeAddressResult.getMessage()+"<br><br>":"").append(triggerResult.getMessage()!=null?triggerResult.getMessage():"");

        // 6、save log trigger-info
        jobLog.setExecutorAddress(address);
        jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
        jobLog.setExecutorParam(jobInfo.getExecutorParam());
        jobLog.setExecutorShardingParam(shardingParam);
        jobLog.setExecutorFailRetryCount(finalFailRetryCount);
        //jobLog.setTriggerTime();
        jobLog.setTriggerCode(triggerResult.getCode());
        jobLog.setTriggerMsg(triggerMsgSb.toString());
        FbsJobAdminConfig.getAdminConfig().getFbsJobLogMapper().updateTriggerInfo(jobLog);

        logger.debug(">>>>>>>>>>> fbs-job trigger end, jobId:{}", jobLog.getId());
    }


    /**
     * run executor
     * @param triggerParam
     * @param address
     * @return
     */
    public static ResultT<String> runExecutor(TriggerParam triggerParam, String address){
        ResultT<String> runResult = null;
        try {
            ExecutorBiz executorBiz = FbsJobScheduler.getExecutorBiz(address);
            runResult = executorBiz.run(triggerParam);
        } catch (Exception e) {
            logger.error(">>>>>>>>>>> fbs-job trigger error, please check if the executor[{}] is running.", address, e);
            runResult = new ResultT<String>(ResultT.FAIL_CODE, e.getMessage());
        }

        StringBuffer runResultSB = new StringBuffer( "触发调度：");
        runResultSB.append("<br>address：").append(address);
        runResultSB.append("<br>code：").append(runResult.getCode());
        runResultSB.append("<br>msg：").append(runResult.getMessage());

        runResult.setMsg(runResultSB.toString());
        return runResult;
    }

}
